/**
 * @file: main.cpp
 * @author: Lynxi SDK Team
 * @brief
 * @version: 1.0
 * @date 2022-10-10
 *
 * Copyright:
 * © 2018 北京灵汐科技有限公司 版权所有。
 * 注意：以下内容均为北京灵汐科技有限公司原创，未经本公司允许，不得转载，否则将视为侵权；对于不遵守此声明或者其他违法使用以下内容者，本公司依法保留追究权。
 * © 2018 Lynxi Technologies Co., Ltd. All rights reserved.
 * NOTICE: All information contained here is, and remains the property of Lynxi.
 * This file can not be copied or distributed without the permission of Lynxi
 * Technologies Co., Ltd.
 *
 */
#include <assert.h>
#include <stdio.h>
#include <unistd.h>

#include <atomic>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <sstream>
#include <thread>

#include "bufferPool.hpp"
#include "logging.h"
#include "lyn_api.h"
#include "opencv2/opencv.hpp"
#include "stdlib.h"
#include "util.hpp"
#include "CLI/CLI11.hpp" // 引入 CLI11 头文件

using namespace std;

// bufNum不能超过25, 推荐设为5~10即可
const uint8_t inBufNum = 5;
const uint8_t outBufNum = 5;

std::map<std::string, int> mpCount;
std::map<std::string, std::chrono::_V2::system_clock::time_point> mpStart;
std::map<std::string, std::chrono::_V2::system_clock::time_point> mpEnd;

struct ChannelInfo
{
  std::string outputPath;
  std::string channelName;
  int deviceID;
  int channelID;
  lynContext_t *context;
};

/**
 * @brief 输入参数数据结构
 */
typedef struct videoEncodeParam_t
{
  std::string strInputPath = "";
  std::string strOutputPath = "";
  uint32_t width = 0;
  uint32_t height = 0;
  uint32_t channelNum = 1;
} videoEncodeParam;

/**
 * @brief 发送指令回调数据结构
 */
typedef struct sendCbData
{
  std::shared_ptr<BufferPool> pQueue = nullptr;
  void *data = nullptr;
  uint8_t *localData = nullptr;
} sendCbData;

/**
 * @brief 发送指令回调函数
 */
lynError_t SendFrameCallback(void *userData)
{
  sendCbData *pData = (sendCbData *)userData;
  if (pData->data)
  {
    pData->pQueue->Push(pData->data);
  }
  // 执行此回调的时候，localData已用完，释放内存
  if (pData->localData)
  {
    free(pData->localData);
  }

  delete pData;
  return 0;
}

/**
 * @brief 接收指令回调数据结构
 */
typedef struct recvCbData
{
  std::shared_ptr<BufferPool> pQueue;
  lynPacket_t packet;
  FILE *packetWriteFile;
  int deviceID;
  int channelID;
} recvCbData;

/**
 * @brief 输出包写文件
 * @param[in] packet 输出packet
 * @param[in] fileName 输出文件路径
 * @return int
 */
lynError_t WriteOutputPktToFile(const lynCodecBuf_t *packet, FILE *f)
{
  // 获取输出pkt数据长度
  uint32_t data_size = 0;
  lynError_t ret = lynEncGetRemotePacketValidSize(packet, &data_size);
  if (ret != 0)
  {
    loge("lynEncGetRemotePacketValidSize error: %d", ret);
    return ret;
  }

  void *data = malloc(data_size);
  // 将pkt数据从device侧拷回
  ret = lynMemcpy(data, packet->data, data_size, ServerToClient);
  if (ret != 0)
  {
    loge("lynMemcpy data_size error: %d", ret);
    free(data);
    return ret;
  }

  fwrite(data, data_size, 1, f);

  free(data);
  return 0;
}

/**
 * @brief 接收指令回调函数
 */
lynError_t RecvPacketCallback(void *userData)
{
  recvCbData *pData = (recvCbData *)userData;

  std::string mKey =
      std::to_string(pData->deviceID) + std::to_string(pData->channelID);
  mpCount[mKey]++;
  if (mpCount[mKey] == 1)
  {
    mpStart[mKey] = system_clock::now();
  }
  if (mpCount[mKey] == 250)
  {
    mpEnd[mKey] = system_clock::now();
    std::chrono::duration<double> runtime(mpEnd[mKey] - mpStart[mKey]);
    printf("device id: %d ,channel id: %d ,time %.2f, rate %.2f fps\n",
           pData->deviceID, pData->channelID, runtime.count(),
           250 / runtime.count());
  }

  CHECK_ERR(WriteOutputPktToFile(&pData->packet, pData->packetWriteFile));
  if (pData->pQueue)
  {
    pData->pQueue->Push(pData->packet.data);
  }
  delete pData;
  return 0;
}

/**
 * @brief 视频编码
 * @param[in] strInputPath 输入文件路径
 * @param[in] strOutputPath 输出文件路径
 * @param[in] attr 视频编码参数配置
 * @param[in] deviceId 指定运行的设备号
 * @return int
 */
lynError_t VideoEncode(ChannelInfo channelInfo, lynVencAttr_t &attr,
                       videoEncodeParam &inputParam)
{
  lynError_t ret = 0;
  std::string info = "deviceID:" + std::to_string(channelInfo.deviceID) +
                     " channelID:" + std::to_string(channelInfo.channelID) +
                     " video encode sample start";
  std::cout << info << std::endl;

  FILE *infp = fopen(inputParam.strInputPath.c_str(), "r");
  if (!infp)
  {
    loge("Open inputfile failed, path: %s", inputParam.strInputPath.c_str());
    return -1;
  }

  // 创建上下文环境
  lynContext_t *ctx = channelInfo.context;
  CHECK_ERR(lynSetCurrentContext(*ctx));
  CHECK_ERR(lynRegisterErrorHandler(StreamErrorHandler, nullptr));

  // 打开编码器
  lynVencHandle_t vencHdl;
  CHECK_ERR(lynVencOpen(&vencHdl, &attr));
  // 创建发送流
  lynStream_t sendStream = nullptr;
  CHECK_ERR(lynCreateStream(&sendStream));

  // 创建接收流
  lynStream_t recvStream = nullptr;
  CHECK_ERR(lynCreateStream(&recvStream));

  // 创建发送和接收buf内存池
  uint64_t bufSize = attr.width * attr.height * 3 / 2; // nv12格式帧大小
  std::shared_ptr<BufferPool> pQueue =
      std::make_shared<BufferPool>(bufSize, inBufNum);
  std::shared_ptr<BufferPool> pOutQueue =
      std::make_shared<BufferPool>(bufSize, outBufNum);

  lynFrame_t *frame = new lynFrame_t;
  frame->size = bufSize;
  frame->eos = false;

  lynPacket_t *packet = new lynPacket_t;
  packet->size = bufSize;
  packet->eos = false;

  // 发送获取sps包指令并添加回调
  packet->data = (uint8_t *)pOutQueue->Pop();
  CHECK_ERR(lynVencGetParamsSetAsync(recvStream, vencHdl, packet));

  FILE *packetWriteFile = fopen(channelInfo.outputPath.c_str(), "wb");
  if (!packetWriteFile)
  {
    loge("Open output file failed, path: %s", channelInfo.outputPath.c_str());
    exit(1);
  }

  recvCbData *params_data = new recvCbData;
  params_data->pQueue = pOutQueue;
  params_data->packet = *packet;
  params_data->packetWriteFile = packetWriteFile; // inputParam.strOutputPath;

  CHECK_ERR(
      lynStreamAddCallback(recvStream, RecvPacketCallback, params_data));

  fseek(infp, 0, SEEK_END);
  uint64_t inFileSize = ftell(infp);

  int repeatTimes = 10;
  int currentNum = 0;
  int itemNum = 30;

  // 循环发送异步编码发送和接收指令
  fseek(infp, 0, SEEK_SET);
  while (true)
  {
    uint8_t *yuvData = (uint8_t *)malloc(bufSize);
    uint64_t size = fread(yuvData, 1, bufSize, infp);
    if (size != bufSize) {
      fseek(infp, 0, SEEK_SET);
      free(yuvData);
      continue;
    }
    currentNum++;

    if (repeatTimes * itemNum == currentNum)
    {
      fclose(infp);
      free(yuvData);
      break;
    }
    else
    {
      inFileSize -= bufSize;
      frame->data = (uint8_t *)pQueue->Pop();
      if (repeatTimes * itemNum - 1 == currentNum)
      {
        frame->eos = 1; // 则当前帧为尾帧
      }
      // 异步拷贝
      CHECK_ERR(lynMemcpyAsync(sendStream, frame->data, yuvData, bufSize, ClientToServer));
    }
    CHECK_ERR(lynVencSendFrameAsync(sendStream, vencHdl, frame));

    sendCbData *data = new sendCbData;
    data->pQueue = pQueue;
    data->data = frame->data;
    data->localData = yuvData;
    CHECK_ERR(lynStreamAddCallback(sendStream, SendFrameCallback, data));

    packet->data = (uint8_t *)pOutQueue->Pop();
    CHECK_ERR(lynVencRecvPacketAsync(recvStream, vencHdl, packet));

    recvCbData *out_data = new recvCbData;
    out_data->pQueue = pOutQueue;
    out_data->packet = *packet;
    out_data->packetWriteFile = packetWriteFile; // inputParam.strOutputPath;
    out_data->channelID = channelInfo.channelID;
    out_data->deviceID = channelInfo.deviceID;

    CHECK_ERR(
        lynStreamAddCallback(recvStream, RecvPacketCallback, out_data));
  }
  // 资源释放与清理
  CHECK_ERR(lynSynchronizeStream(sendStream));
  CHECK_ERR(lynSynchronizeStream(recvStream));
  CHECK_ERR(lynDestroyStream(sendStream));
  CHECK_ERR(lynDestroyStream(recvStream));
  CHECK_ERR(lynVencClose(vencHdl));
  fclose(packetWriteFile);
  delete frame;
  delete packet;
  pQueue = nullptr;
  pOutQueue = nullptr;

  std::string info1 = "deviceID:" + std::to_string(channelInfo.deviceID) +
                      " channelID:" + std::to_string(channelInfo.channelID) +
                      " video encode sample finish";
  std::cout << info1 << std::endl;
  return ret;
}

bool argsParser(int argc, char *argv[], videoEncodeParam &encParam)
{
  std::string argChips;
  CLI::App app;
  // 添加选项
  app.add_option("-i", encParam.strInputPath, "input yuv file path")->default_val("../data/mc_video_encode/1080p_30frms.yuv");
  app.add_option("-o", encParam.strOutputPath, "output file path")->default_val("../data/");
  app.add_option("-w", encParam.width, "input yuv image width")->default_val(1920);
  app.add_option("-y", encParam.height, "input yuv image height")->default_val(1080);
  app.add_option("-d", argChips, "lynxi device id")->default_val("0");
  app.add_option("-c", encParam.channelNum, "process channel count")->default_val(1);

  CLI11_PARSE(app, argc, argv);

  if (!ArgsParse::setChips(argChips.c_str(), ','))
  {
    return false;
  }

  if (ArgsParse::argChipsVec.size() == 0)
  {
    ArgsParse::argChipsVec.push_back(0);
  }

  return true;
}

int main(int argc, char *argv[])
{
  lynError_t ret = 0;
  // 参数解析
  int opt = -1;
  videoEncodeParam encParam;
  encParam.strInputPath = "../data/1080p_30frms.yuv";
  encParam.strOutputPath = "../data/";
  encParam.width = 1920;
  encParam.height = 1080;
  argsParser(argc, argv, encParam);

  // 编码参数设置
  lynVencAttr_t attr;
  lynVencSetDefaultParams(&attr);
  attr.width = encParam.width;
  attr.height = encParam.height;

  attr.bframesNum = 0;
  attr.pframesNum = 10;
  attr.codecType = LYN_CODEC_ID_H264;
  attr.inputFormat = LYN_PIX_FMT_NV12;
  attr.level = 12;
  attr.rateControl = LYN_CODEC_RATE_CONTROL_MODE_CONSTANT;
  attr.targetBitrate = 4096000;
  attr.fps = 12;

  std::vector<lynContext_t *> contextVec;
  std::vector<ChannelInfo> channelInfoVec;
  for (int32_t i = 0; i < ArgsParse::argChipsVec.size(); i++)
  {
    lynContext_t *context = new lynContext_t;
    CHECK_ERR(lynCreateContext(context, i));
    CHECK_ERR(lynRegisterErrorHandler(StreamErrorHandler, nullptr));
    for (uint32_t j = 0; j < encParam.channelNum; j++)
    {
      ChannelInfo channelInfo;
      channelInfo.context = context;
      channelInfo.deviceID = i;
      channelInfo.channelID = j;
      channelInfo.channelName = "mc_device" + std::to_string(i) + "_channel" +
                                std::to_string(j) + ".h264";
      channelInfo.outputPath = encParam.strOutputPath + channelInfo.channelName;

      channelInfoVec.emplace_back(std::move(channelInfo));

      std::string mKey = std::to_string(i) + std::to_string(j);
      mpCount[mKey] = 0;
    }
    contextVec.emplace_back(context);
  }

  std::vector<std::thread> vecThread;
  for (uint32_t i = 0; i < channelInfoVec.size(); ++i)
  {
    std::thread oThreadTmp(VideoEncode, channelInfoVec[i], std::ref(attr),
                           std::ref(encParam));
    vecThread.push_back(std::move(oThreadTmp));
  }

  // 4. 等待线程结束
  for (size_t i = 0; i < vecThread.size(); ++i)
  {
    vecThread[i].join();
  }

  // 5. 释放资源
  for (auto &devCtx : contextVec)
  {
    if (devCtx)
    {
      CHECK_ERR(lynSetCurrentContext(*devCtx));
      CHECK_ERR(lynDestroyContext(*devCtx));
      delete devCtx;
    }
  }

  return 0;
}